﻿#region Header

/*
Copyright (c) 2009, G.W. van der Vegt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary fForm must reproduce the above copyright notice, this list of conditions and
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of G.W. van der Vegt nor the names of its contributors may be
  used to endorse or promote products derived from this software without specific prior written
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*----------   ---   -------------------------------------------------------------------------------
 * Purpose:           Picasa WebAlbum Downloader
 * By:                G.W. van der Vegt (wvd_vegt@knoware.nl)
 * Url:               http: * picasadownloader.codeplex.com
 * Depends:           Newtonsoft.JSON
 *                    Swiss.EncryptIt
 *                    Swiss.IniFile
 *                    Swiss.Overlays
 *                    Swiss.MsGTranslationDlg
 *                    Swiss.WaitCursor
 * License:           New BSD License
 * ----------   ---   -------------------------------------------------------------------------------
 * dd-mm-yyyy - who - description
 * ----------   ---   -------------------------------------------------------------------------------
 * 27-11-2009 - veg - Created.
 * ----------   ---   -------------------------------------------------------------------------------
 * 02-1202009 - veg - Fixed Download Directory (Now a PicasaDownloader folder under My Pictures).
 *                  - Replaced Label by a LinkLabel that opens explorer in the Download Directory.
 *                  - Corrected the Installer Path (Removed Setup Suffix).
 * ----------   ---   -------------------------------------------------------------------------------
 * 09-12-2009 - veg - Anchored linklabel correctly.
 *                  - Added a separate method ListAlbums.
 *                  - Added exception around json retrieval (GetAlum/ListAlbums).
 *                  - Added Updated Column.
 *                  - Refactored a Illegal Characters Method.
 *                  - Fixed http: * picasadownloader.codeplex.com/WorkItem/View.aspx?WorkItemId=9648
 *                  - Fixed http: * picasadownloader.codeplex.com/WorkItem/View.aspx?WorkItemId=9649
 *                  - Added WaitCursor
 *                  - Added Tooltip with some rudimentairy Album/Image Info.
 *                    Fixes: http: * picasadownloader.codeplex.com/WorkItem/View.aspx?WorkItemId=9583
 *                  - Initial focus on UserId TextBox.
 *                  - Added DoubleClicking Albums as alternative for Properties Context MenuItem.
 *                  - Merged Newtonsoft.Json.dll into the main executable to have a portable app.
 *                  - Deployed at Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 11-12-2009 - veg - Added a preview based on a dynamically generated web page.
 *                  - Made preview XHTML 1.0 Strict (passes w3c test at http: * validator.w3.org/)!
 *                  - Switched to a slightly larger thumbnail size if available.
 *                  - Deployed at Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 31-12-2009 - veg - Added Rudementary UI CustomTranslation option.
 *                  - Added CustomTranslation suggestion by Google Translate (html scraping).
 *                  - Added String enum based on http: * blog.waynehartman.com/articles/84.aspx
 *                  - Pattern Items are OnTranslated too by pulling them throught Rc/RcStrings.
 *                  - Added TranslatingTo ComboBox based on http: * tipsntricksbd.blogspot.com/2007/12/combobox-is-one-of-most-common-gui.html
 *                  - Used HttpUltility.HtmlDecode to iron out Html Entities when translating.
 * ----------   ---   -------------------------------------------------------------------------------
 * 01-01-2010 - veg - Found stuff on localization at:
 *                      http: * www.codeproject.com/KB/locale/GlobalizationSample.aspx
 *                      http: * msdn.microsoft.com/en-us/library/y99d1cd3(VS.80).aspx
 *                      http: * msdn.microsoft.com/en-us/library/s9eey0h7(VS.80).aspx
 *                      http: * msdn.microsoft.com/en-us/library/ekyft91f(VS.71).aspx
 *                  - Totally rewrote the translation stuff (using a CustomTranslation Dialog Component now).
 * ----------   ---   -------------------------------------------------------------------------------
 * 03-01-2010 - veg - Naming of ListView columns does not stick. So implemented rudimentary Array Index support.
 *                  - Moved translation into this Form as a Subclass of TranslationDlg.Transaltions.
 * ----------   ---   -------------------------------------------------------------------------------
 * 06-01-2010 - veg - Added Abort Option.
 *                  - Moved TranslationDlg into separate dll and uploaded it as part of the Swiss suite at swiss.codeplex.com.
 *                  - Added Autocompletion, see: http: * www.c-sharpcorner.com/UploadFile/mahesh/AutoCompletion02012006113508AM/AutoCompletion.aspx.
 *                  - Added Change Download Directory under the right mouse button of the InfoLabel.
 * ----------   ---   -------------------------------------------------------------------------------
 * 07-01-2010 - veg - Changed the MyDownloadDir property so it saves and loads from the IniFile.
 *                  - Published at CodePlex as 38388
 * ----------   ---   -------------------------------------------------------------------------------
 * 10-01-2010 - veg - Fixed [workitem:9965], Unhandled error when user does not have public albums.
 *                  - Added LinkLabel message when user is not found.
 * ----------   ---   -------------------------------------------------------------------------------
 * 26-02-2010 - veg - Added experimental support for a modifier of the image download url (insert a d/ before the filename)
 *                  - Added ctrl-click detection to the download button to activate above feature.
 *                  - Added a tooltip to the download button.
 *                  - Repaired a minor bug in the Abort option (Escape Key).
 *                  - Added some debugging output for the experimental fature.
 *                  - Stored the modifier in the ini origin. (if not "d/" it can be found
 *                    in the download button of picasa when viewing a single photo.
 *                  - Made sure the download directory path alsways ends in a pathdelimiter.
 *                  - Fixed a small typo in the English Translation (orginal -> original).
 *                  - Uploaded to Codeplex (but it turned out to be the old version).
 * ----------   ---   -------------------------------------------------------------------------------
 * 28-02-2010 - veg - Uploaded old installer (even after a solution rebuild).
 *                  - Create directory after generating the local filename in case the pattern leads to a new directory name.
 *                  - Added some safeguards to TranslationDlg so it does not translates null properties.
 *                  - Made CurrentCultureInfo and CurrentRegionInfo private for now (so they are not translated).
 *                  - Added Album to PatternItems and Translation.
 *                  - Removed creation of Directory before translation. Now ..\{Owner} also works.
 *                  - Uploaded to Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 05-02-2010 - veg - Added a summary dialog that shows up with any errors.
 *                  - Auto Incremented Build Number on Rebuild
 *                  - Corrected IlMerge Post-Build command (TranslationDlg was missing).
 * ----------   ---   -------------------------------------------------------------------------------
 * 27-06-2012 - veg - Fixed url encoded characters %nn in image names.
 *                    Note these are escaped multiple times so we unescape as long as we
 *                    find %nn patterns in the image file name.
 *                  - Switched to .NET 4.0 Full.
 *                  - Swapped assemblies for newer swiss ones.
 *                  - Removed post build as it does not function at the moment.
 *                    Fortunately ilMergeGui does do the job (but does not generate a commandline yet
 *                    not does it allow you to choose an xml or restore it properly,
 *                    but that will be fixed soon at http: * ilmergegui.codeplex.com !).
 *                  - Swapped Backgroundworker for Task Parallel Framework.
 *                  - Removed backgroundworker code.
 *                  - Added working cancellation option (Task's CancellationToken failed so we just
 *                    exit the loop after an image is downloaded).
 *                  - Google translate is no longer working, needs to be replace by another free service..
 *                  - Uploaded to Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 29-06-2012 - veg - Fixed %nn pattern to honor hexadecimal codes too.
 *                  - Fixed missing {Original Name} pattern by appending the filename to the pattern.
 *                  - Uploaded to Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 23-12-2012 - veg - See https://developers.google.com/picasa-web/docs/2.0/reference#Visibility
 *                  - Found solution for fetching more albums than 1000 (start-index + max-results).
 *                  - Method does not work, but lets me display progress (steps of 100 albums).
 *                  - Added a unsupported method for listing all albums.
 *                    The main web page contains a piece of json at the end.
 *                    By adding ?showall=true to the users landing page we get the page with all albums.
 *                    Then we extract the json from the page footer and parse it.
 *                    Because of the size, max timeout is neccessary.
 *                    Option added as Ctrl-Click.
 *                   -Resume option should also working (right click a album).
 * ----------   ---   -------------------------------------------------------------------------------
 * 26-12-2012 - veg - Added some extra safeguards around setting of progressBar1.Value.
 *                  - Changed download all and continue download to work on all selected listview items.
 *                  - Added a small delay after downloading of an ablbum to let progressbar catchup.
 *                  - Reset Abort for Continue Download.
 *                  - Added Alphanumeric Sorting of Columns.
 * ----------   ---   -------------------------------------------------------------------------------
 * 27-12-2012 - veg - Added version number to AboutDialog.
 *                  - Added Sorting of ListView (including Sort Glyphs).
 *                    See http://social.msdn.microsoft.com/forums/en-US/winformsdesigner/thread/b8c63346-8c1e-4a46-938b-94cc46d6d8b3/
 *                    See http://stackoverflow.com/questions/254129/how-to-i-display-a-sort-arrow-in-the-header-of-a-list-view-column-using-c
 *                  - Adjusted sorting for Date Updated Column.
 *                  - Set Version to v1.0.9.0.
 *                  - Uploaded to Codeplex.
 * ----------   ---   -------------------------------------------------------------------------------
 * 27-12-2012 - veg - Changed default/initial sort order to none.
 * ----------   ---   -------------------------------------------------------------------------------
 * 28-12-2012 - veg - Simplyfied code in Download All Context MenuItem by just calling getAlbums.
 *                  - Added Ctrl support for Download All Context MenuItem.
 *                  - This also corrects disabling the Download Button and Enabling the Stop one.
 *                  - Added some Application.DoEvents() + Thread.Sleep(0) to some tight loop.
 *                  - Added Hint to Swiss.InputDialog.
 *                  - Escape illegal characters in filename and path by an undescore.
 *                  - Rewrote Patterns and Settings to be mapped on PicasaOptions (ala Hottack32).
 *                  - Added Updated Date to Patterns.
 *                    NOTE Due to differences in obtaining the Updated Date, the Downloaded indication might fail.
 *                  - Removed Swiss.InputDialog.
 *                  - Refactored ReplacePatterns into a single method and re-used code.
 *                  - Refactored GetLocalDirectory and GetLocalfilename into a singe method and re-used code.
 *                  - Scroll Listview when Downloading.
 *                  - Added F1 Help support to LocalPropertiesOverlay.
 *                  - Use DateTime.Parse when possible to obtain Updated Date.
 *                  - Removed unused code, properties and fields.
 *                  - Changed new IniFile(Filename) to new IniIni(null) (more uptodate).
 *                  - Found a workaround for the GDI+ errors during Bitmap.Save().
 *                  - Set Version to v1.0.10.0.
 * ----------   ---   -------------------------------------------------------------------------------
 * 30-12-2012 - veg - Added Trayicon with BallonHelp and Context Menu.
 *                  - Added indication of download mode to Window Caption.
 *                  - Added support for CTRL-A  (Select All).
 *                  - Added support for CTRL-C  (Continue Download at Selected Resolution).
 *                  - Added support for CTRL-D  (Download Selected at Original Resolution).
 *                  - Added support for SHIFT-D (Download Selected).
 *                  - The Download and Download all options now reset the download count so
 *                    they download full albums instead of resuming a download at the point of abort.
 *                  - Set focus back to listview because of key handling.
 *                  - Set Version to v1.0.11.0.
 * ----------   ---   -------------------------------------------------------------------------------
 * 31-12-2013 - veg - Added ClickOnece Update Check MenuItem!
 *                  - Updated Paypal Donate button.
 * 01-01-2014 - veg - Added Visit Website MenuItem.
 *                  - Set Version to v1.0.21.0.
 * ----------   ---   -------------------------------------------------------------------------------
 * TODO         veg +       - Add Set Language Menu (with available translations).
 * TODO         veg +       - Adjust Buttons to Translated Widths.
 * TODO         veg + 15762 - Add a small menubar for accessing a setup dialog/about dialog and toggle between default and full-sized downloads. Also add a About dialog.
 *
 * TODO         veg + 15732 - Add option to open browser to picasaweb? Can't find url with both user/albumid, only userid/albumname.
 * TODO         veg + 15729 - Separate base download directory from naming pattern (so naming pattern is only about the subdirectory and filename).
 * TODO         veg + 14712 - Any chance of keeping the original image file date (as uploaded to Picasa).
 * ----------   ---   -------------------------------------------------------------------------------
 * NOTE             - Illegal Characters in filenames:
 *
 *                    The following characters are invalid as origin or folder names on Windows using NTFS:
 *                    / ? < > \ : *  | ” and any character you can type with the Ctrl key
 *
 *                    In addition to the above illegal characters the caret ^ is
 *                    also not permitted under Windows Operating Systems using the FAT origin system.
 *
 * NOTE             - CustomTranslation work as follows.
 *                    Each of the Strings used is defined as a enum value in RcStrings ?
 *                    The DescriptionAttribute holds the english translation.
 *                    The T() functions use Rc on RcStrings to lookup already translated strings from the IniFile
 *                    The T() function calls Suggest() for untranslated strings.
 *                    Suggest() calls Microsoft Translate for a first 'rough' translation.
 *                    OnTranslated strings are stored in the IniFile.
 *                   -The IniFile is not yet Fully UniCode compatible.
 * ----------   ---   -------------------------------------------------------------------------------
 */

#endregion Header

namespace PicasaDownloader
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    using Newtonsoft.Json.Linq;

    using Swiss;

    /// <summary>
    /// A form 1.
    /// </summary>
    public partial class Form1 : Form
    {
        #region Fields

        /// <summary>
        /// Field that contains translations for this Application.
        /// </summary>
        public static CustomTranslations CustomTranslation;

        /// <summary>
        /// Preformatted URL to retrieve all albums of a picasa user.
        /// </summary>
        internal const String AlbumsUrl = "http://picasaweb.google.com/data/feed/base/user/{0}?alt=json&kind=album&hl=nl&access=public&start-index={1}&max-results={2}";

        //internal CancellationTokenSource cts;
        //internal CancellationToken token;
        /// <summary>
        /// Preformatted URL to retrieve a single album of a picasa user.
        /// </summary>
        internal const String AlbumUrl = "http://picasaweb.google.com/data/feed/base/user/{0}/albumid/{1}?category=photo&alt=json";

        /// <summary>
        /// Preformatted URL to retrieve page with all albums of a picasa user.
        /// </summary>
        internal const String AllAlbumsPageUrl = "http://picasaweb.google.com/{0}?showall=true";

        /// <summary>
        /// A Flag to Signal that teh Stop button has been pressed and Downloading should be Aborted.
        /// </summary>
        internal Boolean Abort = false;

        /// <summary>
        /// The ToolTip used for displaying help on DownloadButton.
        /// </summary>
        internal ToolTip DownloadToolTip = new ToolTip();

        /// <summary>
        /// Flag if ctrl was pressed during download. If so, the FullSizeDownloadModifier will be
        /// inserted into the final image url.
        /// </summary>
        internal Boolean FullSizeDownload = false;

        /// <summary>
        /// The ToolTip used for displaying rudimentary Album Info.
        /// </summary>
        internal ToolTip InfoToolTip = new ToolTip();

        /// <summary>
        /// The ToolTip used for displaying help on Link Label.
        /// </summary>
        internal ToolTip LinkToolTip = new ToolTip();

        /// <summary>
        /// The ToolTip used for displaying List Methods.
        /// </summary>
        internal ToolTip ListToolTip = new ToolTip();

        /// <summary>
        /// The End-User Configurable Settings.
        /// </summary>
        internal PicasaOptions Options;

        /// <summary>
        /// Summary.
        /// </summary>
        internal Summary summary = new Summary();

        /// <summary>
        /// The maximum results.
        /// </summary>
        const Int32 MaxResults = 100;

        /// <summary>
        /// The column sorter.
        /// </summary>
        private PicasaColumnSorter ColumnSorter;

        /// <summary>
        /// true to once.
        /// </summary>
        Boolean once = false;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public Form1()
        {
            InitializeComponent();

            using (IniFile ini = new IniFile(null))
            {
                Language = ini.ReadString("Setup", "Language", "en");
            }

            // Setup Translation.
            CustomTranslation = new CustomTranslations(this);
            Directory.CreateDirectory(MsTranslationDlg.ResourcePath);
            MsTranslationDlg.AccountKey = new PicasaDownloader.AccountKey();
            MsTranslationDlg.InitialTranslation(CustomTranslation);
            MsTranslationDlg.UseLanguageCode = Language;
            MsTranslationDlg.Translate(MsTranslationDlg.UseLanguageCode, CustomTranslation);

            AlbumUser = String.Empty;

            Options = new PicasaOptions();
            Options.LoadOptions();

            if (!Directory.Exists(Options.DownloadPath))
            {
                Directory.CreateDirectory(Options.DownloadPath);
            }

            foreach (ColumnHeader ch in listView1.Columns)
            {
                ch.Width = -2;
            }

            // See http://www.fryan0911.com/2009/07/c-how-to-sort-listview-by-clicked.html
            ColumnSorter = new PicasaColumnSorter();
            ColumnSorter.SortColumn = 0;
            ColumnSorter.Order = SortOrder.None;

            listView1.ListViewItemSorter = ColumnSorter;

            listView1.Sort();
            listView1.SetSortIcon(ColumnSorter.SortColumn, ColumnSorter.Order);
        }

        #endregion Constructors

        #region Enumerations

        /// <summary>
        /// The supported replacement patterns.
        /// </summary>
        internal enum PatternItems
        {
            /// <summary>
            /// An enum constant representing the original name option.
            /// </summary>
            OriginalName,
            /// <summary>
            /// An enum constant representing the number option.
            /// </summary>
            Number,
            /// <summary>
            /// An enum constant representing the identifier option.
            /// </summary>
            Id,
            /// <summary>
            /// An enum constant representing the owner option.
            /// </summary>
            Owner,
            /// <summary>
            /// An enum constant representing the album option.
            /// </summary>
            Album,
            /// <summary>
            /// An enum constant representing the updated option.
            /// </summary>
            Updated,
        }

        #endregion Enumerations

        #region Properties

        /// <summary>
        /// Stores the UserName of the Picasa Album Owner.
        /// </summary>
        ///
        /// <value>
        /// The album user.
        /// </value>
        String AlbumUser
        {
            get
            {
                return textBox1.Text;
            }
            set
            {
                textBox1.Text = value;
            }
        }

        /// <summary>
        /// Returns the UI Language.
        /// </summary>
        ///
        /// <value>
        /// The language.
        /// </value>
        String Language { get; set; }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Advance progressbar by one.
        /// </summary>
        ///
        /// <param name="value"> The value. </param>
        internal void AdvanceProgressbarByOne(Int32 value)
        {
            if (InvokeRequired)
            {
                BeginInvoke((Action<Int32>)AdvanceProgressbarByOne, value);
            }
            else
            {
                progressBar1.Increment(value);
            }
        }

        /// <summary>
        /// Replaces illegal characters in Title with a # sign so it can be used as directory name.
        /// </summary>
        ///
        /// <param name="title"> The Title to cleaup. </param>
        ///
        /// <returns>
        /// A cleaned Title that can be used as Directory Name.
        /// </returns>
        internal String CleanupTitle(String title)
        {
            Char[] Illegals = Path.GetInvalidPathChars();

            String Result = title;

            foreach (Char ch in Illegals)
            {
                Result = Result.Replace(ch, '#');
            }

            return Result;
        }

        /// <summary>
        /// Retrieves a Single Album.
        /// </summary>
        ///
        /// <param name="pae">              The Picasa Album Entry. </param>
        /// <param name="continueDownload"> If true only remaining photo's are downloaded. </param>
        ///
        /// <returns>
        /// The number of Images retrieved.
        /// </returns>
        internal Int32 GetAlbum(PicasaAlbumEntry pae, Boolean continueDownload = false)
        {
            using (new WaitCursor())
            {
                //Seek the Currently Selected ListViewItem so we can use it to display status.
                Int32 ndx = -1;
                foreach (ListViewItem lvi in listView1.Items)
                {
                    if (lvi.Text == pae.Id)
                    {
                        ndx = lvi.Index;
                        break;
                    }
                }

                //Create the Url to retrieve.
                //
                Uri feedurl = new Uri(String.Format("{0}{1}",
                    String.Format(AlbumUrl, AlbumUser, pae.Id),
                    String.IsNullOrEmpty(pae.authKey) ? String.Empty : String.Format("&authkey={0}", pae.authKey)));

                if (!continueDownload)
                {
                    //Handle the JSON returned.
                    //
                    String json = GetWebPage(feedurl);

                    if (!String.IsNullOrEmpty(json))
                    {
                        JObject result = JObject.Parse(json);

                        // File.WriteAllText("album.json", result.ToString());

                        JObject feed = (JObject)result["feed"];
                        JArray entries = (JArray)feed["entry"];

                        //Start retrieving all images.
                        foreach (JObject entry in entries)
                        {
                            //Fill the PicasaEntry structure.
                            //
                            PicasaEntry pe = new PicasaEntry();

                            //Album Level
                            //
                            pe.Album = CleanupTitle(pae.Title);
                            pe.Updated = pae.Updated;

                            //Debug.WriteLine(pae.Title);
                            //Debug.WriteLine((String)feed["title"]["$t"]);
                            //CleanupTitle((String)feed["title"]["$t"]);

                            pe.Title = (String)feed["title"]["$t"];
                            pe.Subtitle = (String)feed["subtitle"]["$t"];
                            String[] split = ((String)feed["id"]["$t"]).Split('/');
                            pe.Owner = split[split.Length - 3];
                            pe.Id = (String)split.Last();

                            //Image Level
                            //
                            String url = (String)entry["content"]["src"];

                            url = url.Replace("%20", " ");
                            url = url.Replace("%20", " ");
                            url = url.Replace("%22", "\"");
                            url = url.Replace("%27", "'");

                            if (FullSizeDownload)
                            {
                                url = url.Insert(url.LastIndexOf(@"/") + 1, Options.FullSizeDownloadModifier);
                            }

                            //http://lh5.ggpht.com/_knB_kE11_Mc/RsFBA7vACnI/AAAAAAAAAbQ/qi-kZKZIY8Y/d/P8125553.JPG
                            //Album Entry Level
                            //
                            Debug.WriteLine("Downloading: \"" + url + "\"");

                            pe.Url = new Uri(url);
                            pe.Title = (String)entry["content"]["title"];
                            pe.Filename = url.Split('/').Last();

                            //Fix Url Encoded Characters in filenames.
                            //Loop until either all patterns are gone or
                            //the string does not vary in size anymore.
                            //This last case should not happen but won't hurt either.
                            //
                            Regex rg = new Regex(@"(%[0-9A-F][0-9A-F])", RegexOptions.Multiline);

                            Int32 l = 0;
                            while (rg.IsMatch(pe.Filename) && l != pe.Filename.Length)
                            {
                                l = pe.Filename.Length;
                                pe.Filename = Uri.UnescapeDataString(pe.Filename);
                            }

                            JArray thumbs = (JArray)entry["media$group"]["media$thumbnail"];
                            pe.ThumbUrl = new Uri((String)thumbs[0]["url"]);

                            if (pae.Photos == null)
                            {
                                pae.Photos = new List<PicasaEntry>();
                            }

                            pe.Number = pae.Photos.Count + 1;

                            pae.Photos.Add(pe);

                            pae.Count = 1 * pae.Photos.Count;
                        }
                    }
                    else
                    {
                        if (ndx != -1)
                        {
                            listView1.Items[ndx].SubItems[listView1.Items[ndx].SubItems.Count - 1].Text = String.Format("{0}", "Error");
                        }
                    }
                }

                //Init the ProgressBar.
                progressBar1.Minimum = 0;
                progressBar1.Value = 0;
                progressBar1.Maximum = pae.Count;
                progressBar1.Value = Math.Max(0, Math.Min(pae.Count - pae.Photos.Count, progressBar1.Maximum));
                progressBar1.Update();

                for (Int32 i = pae.Photos.Count - 1; i >= 0; i--)
                {
                    PicasaEntry pe = pae.Photos[i];

                    if (Abort)
                    {
                        summary.Add(pe.Album, pe.Filename, CustomTranslation.sEscape);

                        break;
                    }

                    if (ndx != -1 && ndx < listView1.Items.Count)
                    {
                        listView1.Items[ndx].SubItems[listView1.Items[ndx].SubItems.Count - 1].Text = String.Format("{0} of {1}", progressBar1.Value + 1, progressBar1.Maximum);
                    }

                    Task<Boolean> task = Task.Factory.StartNew<Boolean>(() => DownloadTask(pe));//, token

                    task.Wait();

                    if (task.Result)
                    {
                        pae.Photos.Remove(pe);
                    }

                    progressBar1.Value = Math.Max(0, Math.Min(pae.Count - pae.Photos.Count, progressBar1.Maximum));
                    progressBar1.Update();

                    Thread.Sleep(0);
                    Application.DoEvents();

                    if (Abort)
                    {
                        break;
                    }
                }

                // Dirty but it works?
                // See http://thestrangertech.blogspot.nl/2012/08/c-fix-progressbar-not-updating-fast.html

                progressBar1.Value = progressBar1.Maximum;
                progressBar1.Refresh();
                Application.DoEvents();

                return pae.Count;
            }
        }

        /// <summary>
        /// Retrieves all Selected Albums.
        /// </summary>
        internal void GetAlbums()
        {
            using (new WaitCursor())
            {
                String cap = this.Text;

                if (FullSizeDownload)
                {
                    this.Text = this.Text + " - Original Resolution Mode";
                }

                summary.Clear();

                Abort = false;

                ToggleEnabled();

                Int32 cnt = listView1.SelectedItems.Count;
                Int32 i = 1;

                //Walk through all selected items and retrieve those Albums.
                //
                foreach (ListViewItem lvi in listView1.SelectedItems)
                {
                    if (Abort)
                    {
                        break;
                    }

                    listView1.EnsureVisible(lvi.Index);

                    InfoLbl.Text = String.Format(CustomTranslation.sFetchingAlbum, i, cnt);
                    this.Update();

                    PicasaAlbumEntry pae = (PicasaAlbumEntry)(lvi.Tag);

                    pae.Count = GetAlbum(pae, pae.Photos.Count > 0 && pae.Photos.Count != pae.Count);
                    lvi.Tag = pae;

                    i++;

                    Thread.Sleep(0);
                    Application.DoEvents();
                }

                InfoLbl.Text = String.Format(CustomTranslation.sAlbumsFetched, cnt);

                this.Text = cap;

                ToggleEnabled();
            }

            if (summary.HasMessages())
            {
                summary.ResizeColumns();
                summary.ShowDialog();
            }

            listView1.Focus();
        }

        /// <summary>
        /// Retrieves Thumbnails Single Album.
        /// </summary>
        ///
        /// <param name="AlbumId"> The Picasa Album Id. </param>
        ///
        /// <returns>
        /// Html to be shown in a browser.
        /// </returns>
        internal String GetAlbumThumbs(String AlbumId)
        {
            String html =
                "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n" +
                "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n" +
                "  <head>\r\n" +
                "    <title>{0} by {1}</title>\r\n" +
                "    <meta name=\"Generator\" content=\"PicasaDownloader\" />\r\n" +
                "    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\r\n" +
                "    <meta name=\"Author\" content=\"{1}\" />\r\n" +
                "    <meta name=\"Keywords\" content=\"Picasa, PicasaDownloader, {1}\" />\r\n" +
                "    <meta name=\"Description\" content=\"{2}\" />\r\n" +
                "    <style type=\"text/css\">\r\n" +
                "{3}" +
                "    </style>\r\n" +
                "  </head>\r\n" +
                "  <body>\r\n" +
                "{4}" +
                "  </body>\r\n" +
                "</html>\r\n";

            String images = String.Empty;

            //Combination of some borders and margins.
            //Without it the images do not flow like a table.
            Int32 extraheight = 2 * (2 + 3); //Last 8 for largest thumbnails.

            String style =
                "body {\r\n" +
                "  background-color:{0};\r\n" +
                "  padding:     3px;\r\n" +
                "}\r\n" +
                "\r\n" +
                "div.img\r\n" +
                "  {\r\n" +
                "  margin:      3px;\r\n" + //extraheight #1
                "  border:      1px solid {2};\r\n" +
                "  background-color:{0}; \r\n" + //Moccasin
                "  height:      auto;\r\n" +
                "  width:       auto;\r\n" +
                "  float:       left;\r\n" +
                "  text-align:  center;\r\n" +
                "  }\r\n" +
                "\r\n" +
                "div.img img\r\n" +
                "  {\r\n" +
                "  display:     inline;\r\n" +
                "  margin:      3px;\r\n" + //extraheight #2
                "  border:      1px solid {0};\r\n" + //extraheight #3, Moccasin
                "  background-color:{0};\r\n" +
                "  }\r\n" +
                "\r\n" +
                "div.img:hover\r\n" +
                "  {\r\n" +
                "  border:      1px solid {1};\r\n" +
                "  }\r\n" +
                "\r\n" +
                "div.img a:hover img\r\n" +
                "  {\r\n" +
                "  border:      1px solid {1};\r\n" +
                "  }\r\n" +
                "\r\n" +
                "div.desc\r\n" +
                "  {\r\n" +
                "  border       medium none;\r\n" +
                "  color:       {2};\r\n" +
                "  background-color:{0};\r\n" +
                "  text-align:  center;\r\n" +
                "  font-weight: normal;\r\n" +
                "  margin:      0px;\r\n" +
                "  padding:     2px;\r\n" +
                "  font:        11px/1.4em Arial, sans-serif;\r\n" +
                "  }\r\n" +
                "\r\n" +
                "div.desc:hover\r\n" +
                "  {\r\n" +
                "  color:       {1};\r\n" +
                "}\r\n";
            style = style.Replace("{0}", PicasaOptions.ColorToXmlAttribute(Options.BackGroundColor, true));
            style = style.Replace("{1}", PicasaOptions.ColorToXmlAttribute(Options.HighLightColor, true));
            style = style.Replace("{2}", PicasaOptions.ColorToXmlAttribute(Options.TextColor, true));

            using (new WaitCursor())
            {
                PicasaEntry pe = new PicasaEntry();
                PicasaAlbumEntry pae = null;

                //Seek the Currently Selected ListViewItem so we can use it to display status.
                Int32 ndx = -1;
                foreach (ListViewItem lvi in listView1.Items)
                {
                    if (lvi.Text == AlbumId)
                    {
                        ndx = lvi.Index;
                        pae = (PicasaAlbumEntry)(lvi.Tag);
                        break;
                    }
                }

                if (ndx == -1 || pae == null)
                {
                    return null;
                }

                // Create the Url to retrieve.
                //! Adding the authkey 
                Uri feedurl = new Uri(String.Format(AlbumUrl, AlbumUser, AlbumId) + (String.IsNullOrEmpty(pae.authKey) ? String.Empty : String.Format("&authkey={0}", pae.authKey)));

                //Handle the JSON returned.
                String json = GetWebPage(feedurl);
                Int32 cnt = 0;

                if (!String.IsNullOrEmpty(json))
                {
                    JObject result = JObject.Parse(json);

                    JObject feed = (JObject)result["feed"];
                    JArray entries = (JArray)feed["entry"];

                    //Fill the PicasaEntry structure.
                    pe.Album = CleanupTitle((String)feed["title"]["$t"]);

                    //! DateTime.Parse gives an error, switched to typecast. 
                    // 
                    //pe.Updated = DateTime.Parse((String)feed["updated"]["$t"]);
                    pe.Updated = (DateTime)feed["updated"]["$t"];

                    pe.Title = (String)feed["title"]["$t"];
                    pe.Subtitle = (String)feed["subtitle"]["$t"];
                    String[] split = ((String)feed["id"]["$t"]).Split('/');
                    pe.Owner = split[split.Length - 3];
                    pe.Id = (String)split.Last();

                    //Start retrieving all images.
                    foreach (JObject entry in entries)
                    {
                        String url = (String)entry["content"]["src"];

                        url = url.Replace("%20", " ");
                        url = url.Replace("%20", " ");
                        url = url.Replace("%22", "\"");
                        url = url.Replace("%27", "'");

                        pe.Url = new Uri(url);
                        pe.Filename = url.Split('/').Last();
                        JArray thumbs = (JArray)entry["media$group"]["media$thumbnail"];

                        //We use one larger than the first thumbs ([0]).
                        Int32 UseThumb = Math.Min(2, thumbs.Count);

                        if (UseThumb > 0)
                        {
                            pe.ThumbUrl = new Uri((String)thumbs[UseThumb - 1]["url"]);

                            Int32 width = (Int32)thumbs[UseThumb - 1]["width"];
                            Int32 height = (Int32)thumbs[UseThumb - 1]["height"];
                            Int32 size = extraheight + Math.Max(width, height);

                            if (cnt == 0)
                            {
                                //
                                //width.height gives rise two styles:
                                //
                                //DIV.img(width)  = width * max(width,heigth)  ie. Landscape
                                //DIV.img(height) = height * max(width,heigth) ie. Portrait
                                //
                                //This way we always have correctlty centered images and the divs all have
                                //the correct height.
                                //
                                //Because this is just an image it looks strange but is correct, because
                                //Portait and Lanscape dimenions are just swapped.
                                //
                                //! now width = 144px and size = 160px;
                                style +=
                                    "div.img" + height.ToString() + " { \r\n" +
                                    "  width:       " + size.ToString() + "px; \r\n" +
                                    "/*height:      " + size.ToString() + "px;\r\n" +
                                    "  the two values below add-up to 154px*/\r\n" +
                                    "  height:      " + (size - 30).ToString() + "px;\r\n" +
                                    "  padding-top: 30px;\r\n" +
                                    "  position:    relative; \r\n" +
                                    "  color:       {0}; \r\n" + //Moccasin
                                    "  border:      medium none; \r\n" +
                                    "  margin:      auto;\r\n" +
                                    "}\r\n";
                                style = style.Replace("{0}", PicasaOptions.ColorToXmlAttribute(Options.BackGroundColor, true));

                                //! Ideal would be to set width:160px; here so both div's are equally sized.
                                //! now width = 97px and size = 113px;
                                style +=
                                    "div.img" + width.ToString() + " { \r\n" +
                                    "  width:       " + size.ToString() + "px; \r\n" + //was width + 16
                                    "  height:      " + size.ToString() + "px; \r\n" +
                                    "  position:    relative; \r\n" +
                                    "  color:       {0}; \r\n" + //Moccasin
                                    "  border:      medium none; \r\n" +
                                    "  margin:      auto;\r\n" +
                                    "}\r\n";
                                style = style.Replace("{0}", PicasaOptions.ColorToXmlAttribute(Options.BackGroundColor, true));
                            }

                            images += String.Format(
                                "<div class=\"img\">\r\n" +
                                "  <div class=\"img" + width.ToString() + "\">\r\n" +
                                "    <a onclick=\"function(){3}window.open(this.href,'_blank');return false;{4}\" href=\"{0}\">\r\n" +
                                "      <img src=\"{1}\" alt=\"{2}\" />\r\n" +
                                "    </a>\r\n" +
                                "  </div>\r\n" +
                                "  <div class=\"desc\">\r\n" +
                                "    {2}\r\n" +
                                "  </div>\r\n" +
                                "</div>\r\n\r\n", pe.Url, pe.ThumbUrl, pe.Filename, '{', '}');

                            cnt++;
                        }
                    }

                    return String.Format(html,
                       pe.Album,
                       pe.Owner,
                       pe.Title,
                       style,
                       images);
                }
                else
                {
                    if (ndx != -1)
                    {
                        listView1.Items[ndx].SubItems[listView1.Items[ndx].SubItems.Count - 1].Text = String.Format("{0}", "Error");
                    }
                }

            }

            return null;
        }

        /// <summary>
        /// Retrieve an Image from a Url.
        /// </summary>
        ///
        /// <param name="pe"> The PicasaEntry of the Image to retrieve. </param>
        ///
        /// <returns>
        /// The Image.
        /// </returns>
        internal Bitmap GetBitmap(PicasaEntry pe)
        {
            //Prepare the web page we will be asking for.
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(pe.Url);

            //Execute the request.
            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)
                request.GetResponse();
            }
            catch (Exception /*e*/)
            {
                summary.Add(pe.Album, pe.Filename, CustomTranslation.sPicasaDown);

                //throw new ApplicationException(CustomTranslation.sPicasaDown);

                return null;
            }

            return new Bitmap(response.GetResponseStream());
        }

        /// <summary>
        /// Retrieve a Web Page.
        /// 
        /// Adapted from http://www.csharp-station.com/HowTo/HttpWebFetch.aspx.
        /// </summary>
        ///
        /// <param name="url">     The Url to retrieve. </param>
        /// <param name="timeout"> Timeout in ms, default 100s. </param>
        ///
        /// <returns>
        /// The content of the retrieved webpage or null if an arror has occurred.
        /// </returns>
        internal String GetWebPage(Uri url, Int32 timeout = 100000)
        {
            using (new WaitCursor())
            {
                //Used to build entire input.
                StringBuilder sb = new StringBuilder();

                try
                {
                    //Used on each read operation.
                    byte[] buf = new byte[8192];

                    //Prepare the web page we will be asking for.
                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                    request.Timeout = timeout;

                    //Execute the request.
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                    //Read data via the response stream.
                    Stream resStream = response.GetResponseStream();

                    string tempString = null;
                    int count = 0;

                    do
                    {
                        //Fill the buffer with data.
                        count = resStream.Read(buf, 0, buf.Length);

                        //Make sure we read some data.
                        if (count != 0)
                        {
                            //Translate from bytes to UTF8 text (ASCII will Fail!).
                            tempString = Encoding.UTF8.GetString(buf, 0, count);

                            //Continue building the string.
                            sb.Append(tempString);
                        }
                    }
                    while (count > 0); // any more data to read?
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);

                    return null;
                }

                //Return response source.
                return sb.ToString();
            }
        }

        /// <summary>
        /// Retrieves the Id's and Titles of all Albums.
        /// </summary>
        ///
        /// <param name="start"> The start. </param>
        /// <param name="fetch"> The fetch. </param>
        ///
        /// <returns>
        /// An Int32.
        /// </returns>
        internal Int32 ListAlbums(Int32 start = 1, Int32 fetch = MaxResults)
        {
            Int32 Result = 0;

            try
            {
                if (start == 1)
                {
                    listView1.Items.Clear();
                }

                DownloadBtn.Enabled = false;

                //Update Status.
                InfoLbl.Text = String.Format(CustomTranslation.sFetchingAlbumsOf, AlbumUser);
                label1.Text = String.Format(CustomTranslation.sAlbumsFetched, start - 1);

                this.Update();

                //Create the Url to retrieve.
                Uri feedurl = new Uri(String.Format(AlbumsUrl, AlbumUser, start, fetch));

                Debug.Print(feedurl.ToString());

                //Handle the JSON returned.
                String json = GetWebPage(feedurl);

                if (!String.IsNullOrEmpty(json))
                {
                    JObject result = JObject.Parse(json);

                    //File.WriteAllText("feed.json", result.ToString());

                    JObject feed = (JObject)result["feed"];
                    JArray entries = (JArray)feed["entry"];

                    if (entries != null)
                    {
                        //Start retrieving the neccesary info on the Albums (AlbumId/Title) and display it.
                        foreach (JObject entry in entries)
                        {
                            //http://picasaweb.google.com/data/entry/base/user/diver.guppie/albumid/5385501273853383457?alt=json&hl=nl
                            String url = (String)entry["id"]["$t"];

                            PicasaAlbumEntry pae = new PicasaAlbumEntry(url.Split('/').Last().Split('?').First());

                            pae.Title = CleanupTitle((String)entry["title"]["$t"]);

                            // ((String)entry["updated"]["$t"])	"2012-06-26T07:00:00.000Z"	string
                            //pae.Published = DateTime.Parse((String)(entry["published"]["$t"]));
                            //pae.Updated = DateTime.Parse((String)entry["updated"]["$t"]);

                            pae.Published = (DateTime)entry["published"]["$t"];
                            pae.Updated = (DateTime)entry["updated"]["$t"];

                            //String[] date = ((String)entry["published"]["$t"]).Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                            //if (date.Length == 3)
                            //{
                            //    pae.Published = new DateTime(Int32.Parse(date[2]), Int32.Parse(date[1]), Int32.Parse(date[0]));
                            //    pae.Updated = pae.Published;
                            //}

                            ListViewItem lvi = listView1.Items.Add(pae.Id);
                            lvi.SubItems.Add(pae.Title);

                            lvi.SubItems.Add(String.Empty); //Status
                            try
                            {
                                lvi.SubItems[lvi.SubItems.Count - 1].Text = pae.Updated.ToShortDateString();
                            }
                            finally
                            {
                                //
                            }

                            //String dir = String.Format("{0}{1} - ({2})", Options.DownloadPath, pae.Id, CleanupTitle(pae.Title));
                            //if (Directory.Exists(dir))

                            //Create Fake PicasaEntry.
                            PicasaEntry pe = new PicasaEntry()
                            {
                                Album = CleanupTitle(pae.Title),
                                Updated = pae.Updated,
                                Filename = String.Empty,
                                Id = pae.Id,
                                Number = 0,
                                Owner = AlbumUser,
                                Subtitle = String.Empty,
                                ThumbUrl = null,
                                Title = CleanupTitle(pae.Title),
                                Url = null
                            };

                            if (Directory.Exists(GetLocalDirectory(pe)))
                            {
                                lvi.SubItems.Add(CustomTranslation.sDownloaded);
                            }
                            else
                            {
                                lvi.SubItems.Add(String.Empty); //Status
                            }

                            lvi.Tag = pae;

                            Result++;
                        }

                        //Adjust width of the ListView columns.
                        foreach (ColumnHeader ch in listView1.Columns)
                        {
                            if (ch.Index != listView1.Columns.Count - 1)
                            {
                                //Size to content
                                ch.Width = -1;
                            }
                            else
                            {
                                //Size to header
                                ch.Width = -2;
                            }
                        }
                    }
                }
                else
                {
                    InfoLbl.Text = CustomTranslation.sUserNotFound;
                }

                //Only store correct AlbumUser names.
                if (listView1.Items.Count != 0 && !textBox1.AutoCompleteCustomSource.Contains(AlbumUser))
                {
                    textBox1.AutoCompleteCustomSource.Add(AlbumUser);
                }
            }
            finally
            {
                //
            }

            return Result;
        }

        /// <summary>
        /// The Bottom of the Html Page with all albums contains a Json listing of the Albums.
        /// </summary>
        ///
        /// <returns>
        /// An Int32.
        /// </returns>
        internal Int32 ListAllAlbums()
        {
            Uri feedurl = new Uri(String.Format(AllAlbumsPageUrl, AlbumUser));

            return ListAllAlbums(feedurl);
        }

        /// <summary>
        /// The Bottom of the Html Page with all albums contains a Json listing of the Albums.
        /// </summary>
        ///
        /// <param name="feedurl"> The feedurl. </param>
        ///
        /// <returns>
        /// An Int32.
        /// </returns>
        internal Int32 ListAllAlbums(Uri feedurl)
        {
            listView1.Items.Clear();

            String fullpage = GetWebPage(feedurl, -1);

            if (fullpage != null && fullpage.IndexOf("_user.albums") != 0)
            {
                fullpage = fullpage.Substring(fullpage.IndexOf("_user.albums =\n") + "_user.albums =\n".Length);
                fullpage = fullpage.Substring(0, fullpage.IndexOf("]\n;") + 3);
                fullpage = fullpage.TrimEnd(';');

                // 1) Reinsert forward slashes.
                fullpage = fullpage.Replace(@"\x2F", "/");

                // 2) Decode escaped html entities (but not &#22;, &#39; as that's a problem in json).
                // 3) See http://stackoverflow.com/questions/2275359/jquery-single-quote-in-json-response
                fullpage = fullpage.Replace(@"\x26", "&");
                fullpage = fullpage.Replace(@"\x3B", ";");

                //File.WriteAllText("fullpage.txt", fullpage);

                fullpage = fullpage.Replace(@"&#22;", @"\" + "\"");
                fullpage = fullpage.Replace(@"&#39;", @"\'");

                fullpage = WebUtility.HtmlDecode(fullpage);

                //File.WriteAllText("fullpage1.txt", fullpage);

                JArray entries = JArray.Parse(fullpage);

                // I \x26lt\x3B3 -> 'I <3 Varsovio'
                // 
                foreach (JObject entry in entries)
                {
                    PicasaAlbumEntry pae = new PicasaAlbumEntry((String)entry["id"]);

                    pae.Title = CleanupTitle((String)entry["title"]);

                    //2,7,2010
                    //d,m,yyyy
                    String[] date = ((String)entry["date"]).Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                    if (date.Length == 3)
                    {
                        pae.Published = new DateTime(Int32.Parse(date[2]), Int32.Parse(date[1]), Int32.Parse(date[0]));
                        pae.Updated = pae.Published;
                    }

                    ListViewItem lvi = listView1.Items.Add(pae.Id);
                    lvi.SubItems.Add(pae.Title);

                    lvi.SubItems.Add(String.Empty); //Status
                    try
                    {
                        lvi.SubItems[lvi.SubItems.Count - 1].Text = pae.Updated.ToShortDateString();

                        //String dir = String.Format("{0}{1} - ({2})", Options.DownloadPath, pae.Id, CleanupTitle(pae.Title));
                        //if (Directory.Exists(dir))

                        //Create Fake PicasaEntry.
                        PicasaEntry pe = new PicasaEntry()
                        {
                            Album = CleanupTitle(pae.Title),
                            Updated = pae.Updated,
                            Filename = String.Empty,
                            Id = pae.Id,
                            Number = 0,
                            Owner = AlbumUser,
                            Subtitle = String.Empty,
                            ThumbUrl = null,
                            Title = CleanupTitle(pae.Title),
                            Url = null
                        };

                        if (Directory.Exists(GetLocalDirectory(pe)))
                        {
                            lvi.SubItems.Add(CustomTranslation.sDownloaded);
                        }
                        else
                        {
                            lvi.SubItems.Add(String.Empty); //Status
                        }

                        lvi.Tag = pae;
                    }
                    finally
                    {
                        //
                    }
                }

                //Adjust width of the ListView columns.
                foreach (ColumnHeader ch in listView1.Columns)
                {
                    if (ch.Index != listView1.Columns.Count - 1)
                    {
                        //Size to content
                        ch.Width = -1;
                    }
                    else
                    {
                        //Size to header
                        ch.Width = -2;
                    }
                }

                //Only store correct AlbumUser names.
                if (listView1.Items.Count != 0 && !textBox1.AutoCompleteCustomSource.Contains(AlbumUser))
                {
                    textBox1.AutoCompleteCustomSource.Add(AlbumUser);
                }
            }

            return listView1.Items.Count;
        }

        /// <summary>
        /// Fetches the Albums Json and generates text for a ToolTip.
        /// </summary>
        internal void ShowAlbumInfoTip()
        {
            using (new WaitCursor())
            {
                if (listView1.SelectedItems.Count != 0 && listView1.SelectedItems[0].Focused)
                {
                    Debug.WriteLine(listView1.SelectedItems[0].Text);

                    String AlbumId = listView1.SelectedItems[0].Text;

                    //Create the Url to retrieve.
                    Uri feedurl = new Uri(String.Format(AlbumUrl, AlbumUser, AlbumId));

                    //Handle the JSON returned.
                    String json = GetWebPage(feedurl);

                    if (!String.IsNullOrEmpty(json))
                    {
                        JObject result = JObject.Parse(json);

                        JObject feed = (JObject)result["feed"];
                        JArray entries = (JArray)feed["entry"];

                        //Generate Text to Display.
                        String ToolTipText = "\r\n";

                        ToolTipText += CustomTranslation.sTitle + " " + (String)feed["title"]["$t"] + "\r\n";
                        if (!String.IsNullOrEmpty((String)feed["subtitle"]["$t"]))
                        {
                            ToolTipText += CustomTranslation.sSubTitle + " " + feed["subtitle"]["$t"].ToString() + "\r\n";
                        }

                        String[] split = ((String)feed["id"]["$t"]).Split('/');
                        ToolTipText += CustomTranslation.sUserId + " " + split[split.Length - 3] + "\r\n";
                        ToolTipText += CustomTranslation.sAlbumId + " " + (String)split.Last() + "\r\n";

                        if (entries.Count != 0 && ((JArray)entries[0]["media$group"]["media$content"]).Count != 0)
                        {
                            ToolTipText += CustomTranslation.sNumberOfPhotos + " " + entries.Count.ToString() + "\r\n";
                            ToolTipText += CustomTranslation.sHeight + " " + entries[0]["media$group"]["media$content"][0]["height"] + "px\r\n";
                            ToolTipText += CustomTranslation.sWidth + " " + entries[0]["media$group"]["media$content"][0]["width"] + "px\r\n";
                            ToolTipText += CustomTranslation.sType + " " + entries[0]["media$group"]["media$content"][0]["type"];
                        }
                        else
                        {
                            ToolTipText += CustomTranslation.sNumberOfPhotos + " " + CustomTranslation.sNone;
                        }

                        //Finally Show ToolTip.
                        InfoToolTip.ToolTipTitle = (String)feed["title"]["$t"];
                        InfoToolTip.Show(ToolTipText, this, InfoLbl.Left, this.Height - 16, 5000);
                    }
                }
            }
        }

        /// <summary>
        /// Event handler. Called by aboutToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new AboutDialog().ShowDialog();
        }

        /// <summary>
        /// Event handler. Called by checkForUpdatesToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ClickOnceUpdater.InstallUpdateSyncWithInfo("http://picasadownloader.codeplex.com/releases/view/latest");
        }

        /// <summary>
        /// Event handler. Called by contextMenuStrip1 for opening events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Cancel event information. </param>
        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            InfoMenuItem.Enabled = (listView1.SelectedItems.Count == 1);

            DownloadAllMenuItem.Enabled = listView1.SelectedItems.Count > 0;

            PreviewMenuItem.Enabled = listView1.SelectedItems.Count == 1;// && String.IsNullOrEmpty((listView1.SelectedItems[0].Tag as PicasaAlbumEntry).authKey);

            foreach (ListViewItem lvi in listView1.SelectedItems)
            {
                ContinueDownloadMenuItem.Enabled = DownloadAllMenuItem.Enabled &&
                     ((PicasaAlbumEntry)lvi.Tag).Photos.Count > 0 &&
                     ((PicasaAlbumEntry)lvi.Tag).Photos.Count != ((PicasaAlbumEntry)lvi.Tag).Count;

                if (ContinueDownloadMenuItem.Enabled)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Event handler. Called by ContinueDownloadMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void ContinueDownloadMenuItem_Click(object sender, EventArgs e)
        {
            using (new WaitCursor())
            {
                String cap = this.Text;

                if (FullSizeDownload)
                {
                    this.Text = this.Text + " - Original Resolution Mode";
                }

                Abort = false;

                ToggleEnabled();

                foreach (ListViewItem lvi in listView1.SelectedItems)
                {
                    PicasaAlbumEntry pae = ((PicasaAlbumEntry)lvi.Tag);
                    pae.Count = GetAlbum(pae, true);
                    lvi.Tag = pae;
                }

                this.Text = cap;

                ToggleEnabled();

                listView1.Focus();
            }
        }

        /// <summary>
        /// Event handler. Called by DownloadAllMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void DownloadAllMenuItem_Click(object sender, EventArgs e)
        {
            DownloadBtn.PerformClick();
        }

        /// <summary>
        /// Retrieve the selected Albums.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void DownloadBtn_Click(object sender, EventArgs e)
        {
            FullSizeDownload = ((Control.ModifierKeys & Keys.Control) == Keys.Control);

            ResetDownloadCount();

            GetAlbums();
        }

        /// <summary>
        /// Background Worker Method.
        /// 
        /// Retrieves an Image described by a PicasaEntry passed as parameter and saves it to disk.
        /// </summary>
        ///
        /// <param name="pe"> The PicasaEntry to retrieve. </param>
        ///
        /// <returns>
        /// true if it succeeds, false if it fails.
        /// </returns>
        private Boolean DownloadTask(PicasaEntry pe)
        {
            Boolean result = false;

            try
            {
                using (Bitmap Result = GetBitmap(pe))
                {

                    if (Result != null)
                    {
                        String dir = GetLocalDirectory(pe);
                        String local = GetLocalFilename(pe);

                        Debug.Print("Local Dir  : \"{0}\"", dir);
                        Debug.Print("Local File : \"{0}\"", local);

                        try
                        {
                            //Create Path if neccesary
                            if (!Directory.Exists(Path.GetDirectoryName(local)))
                            {
                                Directory.CreateDirectory(Path.GetDirectoryName(local));
                            }

                            //see http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/b15357f1-ad9d-4c80-9ec1-92c786cca4e6/
                            //!   Workaround for the 'generic error occurred in GDI+' during save that occurs sometimes.
                            using (Bitmap bm = new Bitmap(Result))
                            {
                                bm.Save(local, ImageFormat.Jpeg);
                            }
                            Debug.WriteLine(String.Format("Saved Image: \"{0} - ({1})\\{2}\" at {3}x{4}", pe.Id, pe.Album, pe.Filename, Result.Width, Result.Height));

                            result = true;
                        }
                        catch (System.Runtime.InteropServices.ExternalException)
                        {
                            summary.Add(pe.Album, pe.Album, String.Format(CustomTranslation.sIssuesWithFile, pe.Filename));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                summary.Add(pe.Album, pe.Filename, ex.Message);
            }

            return result;
        }

        /// <summary>
        /// Event handler. Called by ewxitToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void ewxitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        /// <summary>
        /// Event handler. Called by exitToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        /// <summary>
        /// Fetches the albums.
        /// </summary>
        private void FetchAlbums()
        {
            using (new WaitCursor())
            {
                Abort = false;

                Enabled = false;

                listView1.BeginUpdate();

                switch ((Control.ModifierKeys & Keys.Control) == Keys.Control)
                {
                    //Use Feed
                    case false:

                        //See https://developers.google.com/picasa-web/docs/2.0/reference#Visibility
                        Int32 StartIndex = 1;
                        Int32 Fetched = 0;
                        do
                        {
                            StartIndex += Fetched;
                            Fetched = ListAlbums(StartIndex, MaxResults);
                        }
                        while (Fetched == MaxResults);

                        label1.Text = String.Format(CustomTranslation.sAlbumsFetched, StartIndex + Fetched - 1);

                        break;
                    case true:
                        ListAllAlbums();
                        break;
                }

                Enabled = true;

                listView1.EndUpdate();

                this.Update();

                Thread.Sleep(250);

                InfoLbl.Text = String.Format(CustomTranslation.sPublicAlbumsFound, listView1.Items.Count);

                label1.Text = String.Empty;
            }
        }

        /// <summary>
        /// Fetches shared album from a url containing an authkey.
        /// </summary>
        ///
        /// <param name="album"> The album. </param>
        private void FetchSharedAlbum(String album)
        {
            if (album.Contains("picasaweb.google.com") && album.Contains("authkey="))
            {
                Uri albumUri = new Uri(album);

                Regex rg = new Regex(@"(?:http(?:s?)\://)picasaweb.google.com/data/feed/base/user/(.*)\?");

                String albumHtml = GetWebPage(albumUri);

                Match match = rg.Match(albumHtml);

                // See http://stackoverflow.com/questions/68624/how-to-parse-a-query-string-into-a-namevaluecollection-in-net
                // ! ParseQueryString introduces a nasty dependendcy on system.net.http...
                String[] QueryParts = albumUri.Query.TrimStart('?').Split(new Char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);

                if (QueryParts.Count(p => p.StartsWith("authkey=")) == 1)
                {
                    String[] paths = albumUri.AbsolutePath.Split('/');

                    PicasaAlbumEntry pae = new PicasaAlbumEntry(match.Value.TrimEnd(new Char[] { '?' }).Split('/').Last());

                    pae.Title = paths.Last();
                    pae.authKey = QueryParts.First(p => p.StartsWith("authkey=")).Split(new Char[] { '=' })[1];

                    AlbumUser = paths.Skip(paths.Length - 2).First();

                    listView1.Items.Clear();

                    ListViewItem lvi = listView1.Items.Add(pae.Id);
                    lvi.SubItems.Add(pae.Title);
                    lvi.SubItems.Add("");
                    lvi.SubItems.Add("");
                    lvi.Tag = pae;
                }
            }
            else
            {
                MessageBox.Show("The url does not meet the expectations\r\n\r\n" +
                "1) Start with \"http://picasaweb.google.com/data/feed/base/user/<userid>\"\r\n\r\n" +
                "and\r\n\r\n" +
                "2) Contain an authkey query parameter.", IniFile.AppTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        /// <summary>
        /// Event handler. Called by Form1 for form closed events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Form closed event information. </param>
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            using (IniFile ini = new IniFile(null))
            {
                ini.EraseSection("Autocompletion");
                foreach (String s in textBox1.AutoCompleteCustomSource)
                {
                    ini.WriteString("Autocompletion", (textBox1.AutoCompleteCustomSource.IndexOf(s) + 1).ToString(), s);
                }

                ini.UpdateFile();
            }
        }

        /// <summary>
        /// Event handler. Called by Form1 for key down events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Key event information. </param>
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                Debug.WriteLine("Detected Escape Key");
                Abort = true;

                //cts.Cancel();
            }
            else
            {
                e.Handled = false;
            }
        }

        /// <summary>
        /// Event handler. Called by Form1 for resize events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void Form1_Resize(object sender, EventArgs e)
        {
            if (FormWindowState.Minimized == WindowState)
            {
                MinimizeToTray(true);
            }
        }

        /// <summary>
        /// Sets Focus to the UserId TextBox and Initializes the ToolTip.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void Form1_Shown(object sender, EventArgs e)
        {
            textBox1.Focus();

            this.InfoToolTip.AutoPopDelay = 5000;
            this.InfoToolTip.InitialDelay = 250;
            this.InfoToolTip.ReshowDelay = 50;
            this.InfoToolTip.ShowAlways = true;

            using (IniFile ini = new IniFile(null))
            {
                StringCollection sd = ini.ReadSectionValues("Autocompletion");
                foreach (String s in sd)
                {
                    textBox1.AutoCompleteCustomSource.Add(s);
                }
            }

            this.LinkToolTip.AutoPopDelay = 2500;
            this.LinkToolTip.InitialDelay = 250;
            this.LinkToolTip.ReshowDelay = 50;
            this.LinkToolTip.ShowAlways = true;

            this.LinkToolTip.ToolTipTitle = "Help";
            this.LinkToolTip.SetToolTip(InfoLbl,
                "Click left to open the Download Directory\r\n" +
                "Click right to change the Download Directory");

            this.ListToolTip.AutoPopDelay = 2500;
            this.ListToolTip.InitialDelay = 250;
            this.ListToolTip.ReshowDelay = 50;
            this.ListToolTip.ShowAlways = true;

            this.ListToolTip.ToolTipTitle = "Help";
            this.ListToolTip.SetToolTip(ListBtn,
                "Click to List Albums\r\n" +
                "Ctrl-Click to use (unsupported) alternate method to List more than 1000 Albums");

            this.DownloadToolTip.AutoPopDelay = 2500;
            this.DownloadToolTip.InitialDelay = 250;
            this.DownloadToolTip.ReshowDelay = 50;
            this.DownloadToolTip.ShowAlways = true;

            this.DownloadToolTip.ToolTipTitle = "Help";
            this.DownloadToolTip.SetToolTip(DownloadBtn,
                "Click to Download upto 1600px\r\n" +
                "Ctrl-Click to try to Download at Original Resolution");

            MsTranslationDlg.InitialTranslation(CustomTranslation);

            //Own CustomTranslation Approach...
            if (!MsTranslationDlg.Translate(Language, CustomTranslation))
            {
                //Failure: Clear Language from IniFile...
                using (IniFile ini = new IniFile(null))
                {
                    ini.DeleteKey("Setup", "Language");

                    ini.UpdateFile();
                }
            }
        }

        /// <summary>
        /// Gets local directory.
        /// </summary>
        ///
        /// <param name="pe"> The PicasaEntry of the Image to retrieve. </param>
        ///
        /// <returns>
        /// The local directory.
        /// </returns>
        private string GetLocalDirectory(PicasaEntry pe)
        {
            String pattern = Options.DirPattern;

            if (String.IsNullOrEmpty(pattern.Trim()))
            {
                pattern = "PicasaDownLoader";
            }

            return Path.Combine("{0}{1}", Options.DownloadPath, ReplacePatterns(pe, pattern));
        }

        /// <summary>
        /// Gets local filename.
        /// </summary>
        ///
        /// <param name="pe">        The PicasaEntry of the Image to retrieve. </param>
        /// <param name="allowLtGt"> true to allow, false to deny lt gt. </param>
        ///
        /// <returns>
        /// The local filename.
        /// </returns>
        private String GetLocalFilename(PicasaEntry pe, Boolean allowLtGt = false)
        {
            String pattern = Options.FilePattern;

            if (String.IsNullOrEmpty(pattern.Trim()))
            {
                pattern = pe.Filename;
            }

            return Path.Combine(GetLocalDirectory(pe), ReplacePatterns(pe, pattern));
        }

        /// <summary>
        /// Opens the Explorer at the Download Directory.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Link label link clicked event information. </param>
        private void InfoLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                System.Diagnostics.Process.Start(@"explorer.exe", Options.DownloadPath);
            }
            else
            {
                folderBrowserDialog1.SelectedPath = Options.DownloadPath;
                folderBrowserDialog1.ShowNewFolderButton = true;
                folderBrowserDialog1.Description = "Select the Download Directory";
                if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
                {
                    Options.DownloadPath = folderBrowserDialog1.SelectedPath;
                }
            }
        }

        /// <summary>
        /// Retrieve all Picasa Abums of a Picasa User.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void ListBtn_Click(object sender, EventArgs e)
        {
            if (Uri.IsWellFormedUriString(AlbumUser, UriKind.Absolute))
            {
                // Assume AuthKey
                //
                FetchSharedAlbum(AlbumUser);
            }
            else
            {
                // Assume UserId.
                //
                FetchAlbums();
            }
        }

        /// <summary>
        /// Event handler. Called by listView1 for column click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Column click event information. </param>
        private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine if clicked column is already the column that is being sorted.
            if (e.Column == ColumnSorter.SortColumn)
            {
                // Reverse the current sort direction for this column.
                if (ColumnSorter.Order == System.Windows.Forms.SortOrder.Ascending)
                {
                    ColumnSorter.Order = System.Windows.Forms.SortOrder.Descending;
                }
                else
                {
                    ColumnSorter.Order = System.Windows.Forms.SortOrder.Ascending;
                }
            }
            else
            {
                // Set the column number that is to be sorted; default to ascending.
                ColumnSorter.SortColumn = e.Column;
                ColumnSorter.Order = System.Windows.Forms.SortOrder.Ascending;
            }

            // Perform the sort with these new sort options.
            listView1.Sort();

            listView1.SetSortIcon(ColumnSorter.SortColumn, ColumnSorter.Order);
        }

        /// <summary>
        /// Shows the ToolTip like the Properties Context Menuitem.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void listView1_DoubleClick(object sender, EventArgs e)
        {
            ShowAlbumInfoTip();
        }

        /// <summary>
        /// Enable and Disable buttons.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      List view item selection changed event information. </param>
        private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            DownloadBtn.Enabled = ((ListView)sender).SelectedIndices.Count != 0;
        }

        /// <summary>
        /// Event handler. Called by listView1 for key down events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Key event information. </param>
        private void listView1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                //Ctrl-A = Select All.
                case Keys.A:
                    if (e.Control)
                    {
                        listView1.MultiSelect = true;
                        foreach (ListViewItem item in listView1.Items)
                        {
                            item.Selected = true;
                        }
                    }
                    break;

                //Ctrl-C = Continue Download Selected.
                case Keys.C:
                    if (e.Control)
                    {
                        foreach (ListViewItem lvi in listView1.SelectedItems)
                        {
                            if (((PicasaAlbumEntry)lvi.Tag).Photos.Count > 0 &&
                                  ((PicasaAlbumEntry)lvi.Tag).Photos.Count != ((PicasaAlbumEntry)lvi.Tag).Count)
                            {
                                GetAlbums();

                                break;
                            }
                        }
                    }
                    break;

                //Ctrl-D = Download Selected at Full resolution.
                case Keys.D:
                    FullSizeDownload = e.Control;
                    if (e.Control || e.Shift)
                    {
                        ResetDownloadCount();

                        GetAlbums();
                    }
                    break;

                ////Ctrl-O = Download Selected.
                //case Keys.O:
                //    FullSizeDownload = true;
                //    GetAlbums();
            }
        }

        /// <summary>
        /// Event handler. Called by lp for help requested events.
        /// </summary>
        ///
        /// <param name="sender">   The sender. </param>
        /// <param name="hlpevent"> Help event information. </param>
        void lp_HelpRequested(object sender, HelpEventArgs hlpevent)
        {
            String help = String.Empty;
            foreach (string s in Enum.GetNames(typeof(PatternItems)))
            {
                FieldInfo fi = CustomTranslation.GetType().GetField("s" + s);
                help += "  {" + fi.GetValue(CustomTranslation).ToString() + "}\r\n";
            }
            help += "\r\n" + CustomTranslation.sNotCaseSensitive;

            MessageBox.Show(help, IniFile.AppTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        /// <summary>
        /// Minimize to tray.
        /// </summary>
        ///
        /// <param name="minimize"> true to minimize. </param>
        private void MinimizeToTray(Boolean minimize)
        {
            switch (minimize)
            {
                case true:
                    this.Hide();
                    this.ShowInTaskbar = false;

                    notifyIcon1.Visible = true;

                    if (!once)
                    {
                        notifyIcon1.ShowBalloonTip(1000);
                        once = true;
                    }
                    break;

                case false:
                    Show();
                    WindowState = FormWindowState.Normal;
                    this.ShowInTaskbar = true;

                    notifyIcon1.Visible = false;

                    BringToFront();

                    break;
            }
        }

        /// <summary>
        /// Event handler. Called by notifyIcon1 for mouse click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Mouse event information. </param>
        private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                MinimizeToTray(false);
            }
        }

        /// <summary>
        /// Event handler. Called by previewToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void previewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (new WaitCursor())
            {
                if (listView1.SelectedItems.Count == 1 && listView1.SelectedItems[0].Focused)
                {
                    Debug.WriteLine(listView1.SelectedItems[0].Text);

                    String AlbumId = listView1.SelectedItems[0].Text;

                    String html = GetAlbumThumbs(AlbumId);

                    if (!String.IsNullOrEmpty(html))
                    {
                        String filename = Path.Combine(System.IO.Path.GetTempPath(), "PicasaDownloader.html");
                        File.WriteAllText(filename, html);
                        System.Diagnostics.Process.Start(filename);
                    }
                }
            }
        }

        /// <summary>
        /// Handles the Properties MenuItem by showing a ToolTip,.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>   
        private void propertiesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowAlbumInfoTip();
        }

        /// <summary>
        /// Replace patterns.
        /// </summary>
        ///
        /// <param name="pe">      The PicasaEntry of the Image to retrieve. </param>
        /// <param name="pattern"> Specifies the pattern. </param>
        ///
        /// <returns>
        /// A String.
        /// </returns>
        private String ReplacePatterns(PicasaEntry pe, String pattern)
        {
            String Result = pattern.ToLower();

            Result = Result.Replace("{" + CustomTranslation.sOriginalName.ToLower() + "}", pe.Filename);
            Result = Result.Replace("{" + CustomTranslation.sNumber.ToLower() + "}", pe.Number.ToString("D4"));
            Result = Result.Replace("{" + CustomTranslation.sId.ToLower() + "}", pe.Id.ToString());
            Result = Result.Replace("{" + CustomTranslation.sOwner.ToLower() + "}", pe.Owner.ToString());
            Result = Result.Replace("{" + CustomTranslation.sAlbum.ToLower() + "}", pe.Album.ToString());
            Result = Result.Replace("{" + CustomTranslation.sUpdated.ToLower() + "}", pe.Updated.ToString("yyyy-mm-dd"));

            Char[] Allowed = new Char[] { Path.DirectorySeparatorChar };

            foreach (Char ch in Path.GetInvalidFileNameChars())
            {
                // Allow \ so "{Owner}\{Id} - {Album}" works.
                if (!(Allowed.Contains(ch)))
                {
                    Result = Result.Replace(ch, '_');
                }
            }

            foreach (Char ch in Path.GetInvalidPathChars())
            {
                Result = Result.Replace(ch, '_');
            }

            return Result;
        }

        /// <summary>
        /// Resets the download count.
        /// </summary>
        private void ResetDownloadCount()
        {
            //Reset Download Counts.
            foreach (ListViewItem lvi in listView1.SelectedItems)
            {
                PicasaAlbumEntry pae = ((PicasaAlbumEntry)lvi.Tag);
                pae.Photos.Clear();
                pae.Count = 0;
                lvi.Tag = pae;
            }
        }

        /// <summary>
        /// Event handler. Called by SetupBtn for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void SetupBtn_Click(object sender, EventArgs e)
        {
            LocalPropertiesOverlay lp = new LocalPropertiesOverlay();

            lp.SelectedObject = Options;
            lp.HelpRequested += new HelpEventHandler(lp_HelpRequested);
            lp.Width = 640;
            lp.Height = 320;
            lp.ShowDialog(this);

            Options.SaveOptions();
        }

        /// <summary>
        /// Event handler. Called by StopBtn for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void StopBtn_Click(object sender, EventArgs e)
        {
            //cts.Cancel();

            Abort = true;
        }

        /// <summary>
        /// Event handler. Called by textBox1 for drag drop events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Drag event information. </param>
        private void textBox1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.StringFormat))
            {
                String text = (String)e.Data.GetData(DataFormats.StringFormat);

                if (Uri.IsWellFormedUriString(text, UriKind.Absolute))
                {
                    AlbumUser = text;

                    ListBtn.PerformClick();
                }
            }
        }

        /// <summary>
        /// Event handler. Called by textBox1 for drag enter events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Drag event information. </param>
        private void textBox1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.StringFormat))
            {
                String text = (String)e.Data.GetData(DataFormats.StringFormat);

                if (Uri.IsWellFormedUriString(text, UriKind.Absolute))
                {
                    e.Effect = DragDropEffects.Link;
                }
            }
        }

        /// <summary>
        /// Event handler. Called by textBox1 for drag over events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Drag event information. </param>
        private void textBox1_DragOver(object sender, DragEventArgs e)
        {
            //if (e.Data.GetDataPresent(DataFormats.StringFormat))
            //{
            //    String text = (String)e.Data.GetData(DataFormats.StringFormat);

            //    if (Uri.IsWellFormedUriString(text, UriKind.Absolute))
            //    {
            //        e.Effect = DragDropEffects.All;
            //    }
            //}
        }

        /// <summary>
        /// Lists Albums if Enter is pressed in the UserID TextBox.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Key event information. </param>
        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                Debug.WriteLine("Detected Enter Key");

                if (!String.IsNullOrEmpty(textBox1.Text))
                {
                    FetchAlbums();
                }
            }
        }

        /// <summary>
        /// Enable and Disable buttons.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            ListBtn.Enabled = !String.IsNullOrEmpty(AlbumUser);
        }

        /// <summary>
        /// Toggle enabled.
        /// </summary>
        private void ToggleEnabled()
        {
            DownloadBtn.Enabled = !DownloadBtn.Enabled;
            ListBtn.Enabled = !ListBtn.Enabled;
            SetupBtn.Enabled = !SetupBtn.Enabled;
            StopBtn.Enabled = !StopBtn.Enabled;
            TranslateBtn.Enabled = !TranslateBtn.Enabled;

            menuStrip1.Enabled = !menuStrip1.Enabled;

            listView1.Enabled = !listView1.Enabled;
            textBox1.Enabled = !textBox1.Enabled;
        }

        /// <summary>
        /// Event handler. Called by toolStripMenuItem2 for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void toolStripMenuItem2_Click(object sender, EventArgs e)
        {
            MinimizeToTray(false);
        }

        /// <summary>
        /// Event handler. Called by TranslateBtn for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void TranslateBtn_Click(object sender, EventArgs e)
        {
            if (MsTranslationDlg.Execute(CustomTranslation, Language))
            {
                MsTranslationDlg.Translate(MsTranslationDlg.UseLanguageCode, CustomTranslation);

                using (IniFile ini = new IniFile(null))
                {
                    this.Language = MsTranslationDlg.UseLanguageCode;

                    ini.WriteString("Setup", "Language", Language);

                    ini.UpdateFile();
                }
            }
        }

        /// <summary>
        /// Event handler. Called by visitWebsiteToolStripMenuItem for click events.
        /// </summary>
        ///
        /// <param name="sender"> The sender. </param>
        /// <param name="e">      Event information. </param>
        private void visitWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ClickOnceUpdater.VisitWebsite("http://picasadownloader.codeplex.com/");
        }

        #endregion Methods

        #region Nested Types

        /// <summary>
        /// A picasa album entry.
        /// </summary>
        internal class PicasaAlbumEntry
        {
            #region Fields

            /// <summary>
            /// The authentication key.
            /// </summary>
            internal String authKey;

            /// <summary>
            /// Original number of Photo's (before Downloading).
            /// </summary>
            internal Int32 Count;

            /// <summary>
            /// Album Level.
            /// </summary>
            internal String Id;

            /// <summary>
            /// The Photo's to Download.
            /// </summary>
            internal List<PicasaEntry> Photos;

            /// <summary>
            /// The published Date/Time.
            /// </summary>
            internal DateTime Published;

            /// <summary>
            /// The title.
            /// </summary>
            internal String Title;

            /// <summary>
            /// The updated Date/Time.
            /// </summary>
            internal DateTime Updated;

            #endregion Fields

            #region Constructors

            /// <summary>
            /// Initializes a new instance of the PicasaDownloader.Form1 class.
            /// </summary>
            ///
            /// <param name="Id"> The identifier. </param>
            internal PicasaAlbumEntry(String Id)
            {
                this.Id = Id;
                this.Title = String.Empty;
                this.Published = DateTime.Now;
                this.Updated = DateTime.Now;
                this.Count = 0;
                this.Photos = new List<PicasaEntry>();
                this.authKey = String.Empty;
            }

            /// <summary>
            /// Initializes a new instance of the PicasaDownloader.Form1 class.
            /// </summary>
            ///
            /// <param name="pae"> The pae. </param>
            internal PicasaAlbumEntry(PicasaAlbumEntry pae)
            {
                this.Id = pae.Id;
                this.Title = pae.Title;
                this.Published = pae.Published;
                this.Updated = pae.Updated;
                this.Count = pae.Count;
                this.Photos = pae.Photos;
                this.authKey = pae.authKey;
            }

            #endregion Constructors
        }

        /// <summary>
        /// A structure to store info on the AlbumEntry to retrieve. It also contains some info on the
        /// Album itself.
        /// </summary>
        internal class PicasaEntry
        {
            #region Fields

            /// <summary>
            /// Album Level.
            /// </summary>
            internal String Album;

            /// <summary>
            /// Album Entry Level.
            /// </summary>
            internal String Filename;

            /// <summary>
            /// The identifier.
            /// </summary>
            internal String Id;

            /// <summary>
            /// Number of.
            /// </summary>
            internal Int32 Number;

            /// <summary>
            /// The owner.
            /// </summary>
            internal String Owner;

            /// <summary>
            /// The subtitle.
            /// </summary>
            internal String Subtitle;

            /// <summary>
            /// URL of the thumb.
            /// </summary>
            internal Uri ThumbUrl;

            /// <summary>
            /// The title.
            /// </summary>
            internal String Title;

            /// <summary>
            /// The updated Date/Time.
            /// </summary>
            internal DateTime Updated;

            /// <summary>
            /// URL of the document.
            /// </summary>
            internal Uri Url;

            #endregion Fields
        }

        /// <summary>
        /// This class contains the initial translation for the application.
        /// 
        /// It can consist of:
        ///   a) Fields b) Properties
        /// 
        /// Both types of members must be of the type String and have a DescriptionAttribute for grouping
        /// in the TranslationDlg.
        /// 
        /// Because properties have a Setter they can be used to translate Controls and adjust the user-
        /// interface by coding. For this a reference must be stored to the Form/Forms being transated so
        /// the compiler is able to check references.
        /// 
        /// For adjusting the user-interface, the Translations class implements a SetText that returns
        /// the size and position adjustments the DotNet FrameWork made by setting the Text property.
        /// 
        /// Note: Reflection cannot be used for this as there are unnamed controls (ListViewHeaders) and
        /// objects that are not easily discovered by reflection like ToolStripItems.
        /// </summary>
        public class CustomTranslations : MsTranslationDlg.Translations
        {
            #region Fields

            /// <summary>
            /// Album.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sAlbum = "Album";

            /// <summary>
            /// Album identifier.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sAlbumId = "Album Id:";

            /// <summary>
            /// Albums fetched.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sAlbumsFetched = "{0} Album(s) Fetched.";

            /// <summary>
            /// Downloaded.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sDownloaded = "Downloaded";

            //Setup

            /// <summary>
            /// Specifies the pattern.
            /// </summary>
            [DescriptionAttribute("Setup")]
            public String sEnterNamingPattern = "Enter naming pattern:";

            /// <summary>
            /// Escape.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sEscape = "Aborting download, Escape key pressed.";

            //Messages...

            /// <summary>
            /// Fetching albums.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sFetchingAlbum = "Fetching Album {0} of {1}.";

            /// <summary>
            /// Fetching albums of.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sFetchingAlbumsOf = "Fetching Albums of {0}.";

            /// <summary>
            /// .
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sHeight = "Height:";

            /// <summary>
            /// The identifier.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sId = "Id";

            //Errors...

            /// <summary>
            /// Issue with File.
            /// </summary>
            [DescriptionAttribute("Errors")]
            public String sIssuesWithFile = "Some issue with the file name: {0}!";

            /// <summary>
            /// None.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sNone = "none";

            /// <summary>
            /// Not case sensitive.
            /// </summary>
            [DescriptionAttribute("Setup")]
            public String sNotCaseSensitive = "Items are not case-sensitive.";

            /// <summary>
            /// Number.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sNumber = "Number";

            /// <summary>
            /// The Number of photos.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sNumberOfPhotos = "Number of Photos:";

            //Pattern Items...

            /// <summary>
            /// Name of the original.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sOriginalName = "Original Name";

            /// <summary>
            /// Owner.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sOwner = "Owner";

            /// <summary>
            /// The picasa down.
            /// </summary>
            [DescriptionAttribute("Errors")]
            public String sPicasaDown = "Either Picasa is down or you don't have Internet access or your computer is behind a firewall!";

            /// <summary>
            /// Public albums found.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sPublicAlbumsFound = "{0} Public Album(s) found.";

            /// <summary>
            /// The Subtitle.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sSubTitle = "Subtitle:";

            /// <summary>
            /// Supported Patterns.
            /// </summary>
            [DescriptionAttribute("Setup")]
            public String sSupportedPatterns = "Supported Patterns Items are:";

            /// <summary>
            /// Title.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sTitle = "Title:";

            /// <summary>
            /// Translating.
            /// </summary>
            [DescriptionAttribute("Messages")]
            public String sTranslating = "Translating UI.";

            /// <summary>
            /// Type.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sType = "Type:";

            /// <summary>
            /// Updated.
            /// </summary>
            [DescriptionAttribute("Patterns")]
            public String sUpdated = "Updated";

            /// <summary>
            /// UserID.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sUserId = "User Id:";

            /// <summary>
            /// User Not Found.
            /// </summary>
            [DescriptionAttribute("Errors")]
            public String sUserNotFound = "Picasa user not found!";

            /// <summary>
            /// Width.
            /// </summary>
            [DescriptionAttribute("Tooltip")]
            public String sWidth = "Width:";

            /// <summary>
            /// Form.
            /// </summary>
            private Form1 fForm;

            #endregion Fields

            #region Constructors

            /// <summary>
            /// One must define a construtor that takes the Form1 as parameter to be able to refer to
            /// controls on it in order to translate them and be able to adjust the user-interface.
            /// 
            /// See the code for labels and buttons.
            /// </summary>
            ///
            /// <param name="form"> The Form1 to be Translated. </param>
            public CustomTranslations(Form1 form)
                : base()
            {
                this.fForm = form;
            }

            #endregion Constructors

            #region Properties

            /// <summary>
            /// Translation of a ListViewHeader.
            /// </summary>
            ///
            /// <value>
            /// The c album identifier column.
            /// </value>
            [DescriptionAttribute("Columns")]
            public String cAlbumIdColumn
            {
                get
                {
                    return fForm.AlbumIdColumn.Text;
                }
                set
                {
                    fForm.AlbumIdColumn.Text = value;
                }
            }

            /// <summary>
            /// Translation of a ListViewHeader.
            /// </summary>
            ///
            /// <value>
            /// The c album title column.
            /// </value>
            [DescriptionAttribute("Columns")]
            public String cAlbumTitleColumn
            {
                get
                {
                    return fForm.AlbumTitleColumn.Text;
                }
                set
                {
                    fForm.AlbumTitleColumn.Text = value;
                }
            }

            /// <summary>
            /// Translation of a ToolStripItem.
            /// </summary>
            ///
            /// <value>
            /// The c cancel menu item.
            /// </value>
            [DescriptionAttribute("Menus")]
            public String cCancelMenuItem
            {
                get
                {
                    return fForm.CancelMenuItem.Text;
                }
                set
                {
                    fForm.CancelMenuItem.Text = value;
                }
            }

            /// <summary>
            /// Translation of a Button.
            /// </summary>
            ///
            /// <value>
            /// The c download button.
            /// </value>
            [DescriptionAttribute("Buttons")]
            public String cDownloadBtn
            {
                get
                {
                    return fForm.DownloadBtn.Text;
                }
                set
                {
                    Rectangle deltaBounds = SetText(fForm.DownloadBtn, value);

                    fForm.progressBar1.Width = fForm.progressBar1.Width - (deltaBounds.Width);
                }
            }

            /// <summary>
            /// Translation of a Label.
            /// </summary>
            ///
            /// <value>
            /// The c information label.
            /// </value>
            [DescriptionAttribute("Labels")]
            public String cInfoLbl
            {
                get
                {
                    return fForm.InfoLbl.Text;
                }
                set
                {
                    fForm.InfoLbl.Text = value;
                }
            }

            /// <summary>
            /// Translation of a ToolStripItem.
            /// </summary>
            ///
            /// <value>
            /// The c information menu.
            /// </value>
            [DescriptionAttribute("Menus")]
            public String cInfoMenu
            {
                get
                {
                    return fForm.InfoMenuItem.Text;
                }
                set
                {
                    fForm.InfoMenuItem.Text = value;
                }
            }

            /// <summary>
            /// Translation of a Button.
            /// </summary>
            ///
            /// <value>
            /// The c list button.
            /// </value>
            [DescriptionAttribute("Buttons")]
            public String cListBtn
            {
                get
                {
                    return fForm.ListBtn.Text;
                }
                set
                {
                    Rectangle deltaBounds = SetText(fForm.ListBtn, value);

                    fForm.textBox1.Width = fForm.textBox1.Width - deltaBounds.Width;
                }
            }

            /// <summary>
            /// Translation of a ListViewHeader.
            /// </summary>
            ///
            /// <value>
            /// The total number of photos column.
            /// </value>
            [DescriptionAttribute("Columns")]
            public String cNumberOfPhotosColumn
            {
                get
                {
                    return fForm.NumberOfPhotosColumn.Text;
                }
                set
                {
                    fForm.NumberOfPhotosColumn.Text = value;
                }
            }

            /// <summary>
            /// Translation of a ToolStripItem.
            /// </summary>
            ///
            /// <value>
            /// The c preview menu item.
            /// </value>
            [DescriptionAttribute("Menus")]
            public String cPreviewMenuItem
            {
                get
                {
                    return fForm.PreviewMenuItem.Text;
                }
                set
                {
                    fForm.PreviewMenuItem.Text = value;
                }
            }

            /// <summary>
            /// Translation of a Button.
            /// </summary>
            ///
            /// <value>
            /// The c setup button.
            /// </value>
            [DescriptionAttribute("Buttons")]
            public String cSetupBtn
            {
                get
                {
                    return fForm.SetupBtn.Text;
                }
                set
                {
                    fForm.SetupBtn.Text = value;
                }
            }

            /// <summary>
            /// Buttons.
            /// </summary>
            ///
            /// <value>
            /// The c translate button.
            /// </value>
            [DescriptionAttribute("Buttons")]
            public String cTranslateBtn
            {
                get
                {
                    return fForm.TranslateBtn.Text;
                }
                set
                {
                    fForm.TranslateBtn.Text = value;
                }
            }

            /// <summary>
            /// Translation of a ListViewHeader.
            /// </summary>
            ///
            /// <value>
            /// The c updated column.
            /// </value>
            [DescriptionAttribute("Columns")]
            public String cUpdatedColumn
            {
                get
                {
                    return fForm.UpdatedColumn.Text;
                }
                set
                {
                    fForm.UpdatedColumn.Text = value;
                }
            }

            /// <summary>
            /// Translation of a Label.
            /// </summary>
            ///
            /// <value>
            /// The c user identifier label.
            /// </value>
            [DescriptionAttribute("Labels")]
            public String cUserIdLbl
            {
                get
                {
                    return fForm.UserIdLbl.Text;
                }
                set
                {
                    Rectangle deltaBounds = base.SetText(fForm.UserIdLbl, value);

                    fForm.progressBar1.Width = fForm.progressBar1.Width - (deltaBounds.Width);
                    fForm.textBox1.Width = fForm.textBox1.Width - deltaBounds.Width;
                    fForm.textBox1.Left = fForm.textBox1.Left + deltaBounds.Width;
                }
            }

            #endregion Properties
        }

        /// <summary>
        /// See http://www.fryan0911.com/2009/07/c-how-to-sort-listview-by-clicked.html.
        /// </summary>
        public class PicasaColumnSorter : IComparer
        {
            #region Fields

            /// <summary>
            /// The column to sort.
            /// </summary>
            private int ColumnToSort;

            /// <summary>
            /// The object compare.
            /// </summary>
            private CaseInsensitiveComparer ObjectCompare;

            /// <summary>
            /// The order of sort.
            /// </summary>
            private SortOrder OrderOfSort;

            #endregion Fields

            #region Constructors

            /// <summary>
            /// Constructor.
            /// </summary>
            public PicasaColumnSorter()
            {
                // Initialize the column to '0'
                ColumnToSort = 0;

                // Initialize the sort order to 'none'
                OrderOfSort = SortOrder.None;

                // Initialize the CaseInsensitiveComparer object
                ObjectCompare = new CaseInsensitiveComparer();
            }

            #endregion Constructors

            #region Properties

            /// <summary>
            /// Set the Sorted Column's Sort Order.
            /// </summary>
            ///
            /// <value>
            /// The order.
            /// </value>
            public SortOrder Order
            {
                set
                {
                    OrderOfSort = value;
                }
                get
                {
                    return OrderOfSort;
                }
            }

            /// <summary>
            /// Set the Sort Column Index.
            /// </summary>
            ///
            /// <value>
            /// The sort column.
            /// </value>
            public int SortColumn
            {
                set
                {
                    ColumnToSort = value;
                }
                get
                {
                    return ColumnToSort;
                }
            }

            #endregion Properties

            #region Methods

            /// <summary>
            /// IComparer Interface.
            /// </summary>
            ///
            /// <param name="x"> The first object to compare. </param>
            /// <param name="y"> The second object to compare. </param>
            ///
            /// <returns>
            /// Negative if 'x' is less than 'y', 0 if they are equal, or positive if it is greater.
            /// </returns>
            public int Compare(object x, object y)
            {
                // Default is equal.
                int compareResult = 0;

                ListViewItem listviewX, listviewY;

                // Cast the objects to be compared to ListViewItem objects
                listviewX = (ListViewItem)x;
                listviewY = (ListViewItem)y;

                // Bail out on problems.
                if (ColumnToSort >= listviewX.SubItems.Count || ColumnToSort >= listviewY.SubItems.Count)
                {
                    return compareResult;
                }

                // Compare the two items
                switch (ColumnToSort)
                {
                    //Date Column.
                    case 2:
                        compareResult = DateTime.Compare(((PicasaAlbumEntry)listviewX.Tag).Updated, ((PicasaAlbumEntry)listviewY.Tag).Updated);
                        break;
                    default:
                        compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
                        break;
                }

                // Calculate correct return value based on object comparison
                if (OrderOfSort == SortOrder.Ascending)
                {
                    // Ascending sort is selected, return normal result of compare operation
                    return compareResult;
                }
                else if (OrderOfSort == SortOrder.Descending)
                {
                    // Descending sort is selected, return negative result of compare operation
                    return (-compareResult);
                }
                else
                {
                    // Return '0' to indicate they are equal
                    return 0;
                }
            }

            #endregion Methods
        }

        /// <summary>
        /// Graph Options.
        /// </summary>
        public class PicasaOptions
        {
            #region Constructors

            /// <summary>
            /// Constructor.
            /// </summary>
            public PicasaOptions()
            {
                using (IniFile ini = new IniFile(null))
                {
                    String path = ini.ReadString("Setup", "DownloadPath", String.Format(@"{0}\PicasaDownloaded\", Pictures));

                    if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
                    {
                        path += Path.DirectorySeparatorChar;
                    }

                    if (ini.ValueExists("Setup", "DownloadPath") && Directory.Exists(path))
                    {
                        //this.DownloadPath = path;
                    }
                    else
                    {
                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }
                    }

                    this.DownloadPath = path;

                    this.DirPattern = ("{" + CustomTranslation.sId + "} - ({" + CustomTranslation.sAlbum + "})");
                    this.FilePattern = ("{" + CustomTranslation.sOriginalName + "}");
                    this.FullSizeDownloadModifier = "d/";
                    this.BackGroundColor = SystemColors.ControlDarkDark;
                    this.HighLightColor = Color.Yellow;
                    this.TextColor = Color.LightGray;
                }
            }

            /// <summary>
            /// Initializes a new instance of the PicasaDownloader.Form1.PicasaOptions class.
            /// </summary>
            ///
            /// <param name="DownloadPath"> The full pathname of the download file. </param>
            /// <param name="DirPattern">   The dir pattern. </param>
            /// <param name="FilePattern">  The file pattern. </param>
            public PicasaOptions(String DownloadPath, String DirPattern, String FilePattern)
                : this()
            {
                this.DownloadPath = DownloadPath;
                this.DirPattern = DirPattern;
                this.FilePattern = FilePattern;
                //this.FullSizeDownloadModifier = "d/";
                //this.BackGroundColor = SystemColors.ControlDarkDark;
                //this.TextColor = Color.White;
            }

            #endregion Constructors

            #region Properties

            /// <summary>
            /// This is the Background Color of Preview.
            /// </summary>
            ///
            /// <value>
            /// The color of the back ground.
            /// </value>
            [Description("This is the Background Color of Preview.")]
            [Category("Tweaks")]
            [DefaultValue(typeof(Color), "ControlDarkDark")]
            public Color BackGroundColor { get; set; }

            /// <summary>
            /// This is the Path that is appended to the DownloadPath.
            /// </summary>
            ///
            /// <value>
            /// The dir pattern.
            /// </value>
            [Description("This is the Path Pattern that is appended to the DownloadPath.\r\n\r\nPress F1 for available Patterns.")]
            [Category("Patterns")]
            public String DirPattern { get; set; }

            /// <summary>
            /// This is the Path that is prepended to the Pattern.
            /// </summary>
            ///
            /// <value>
            /// The full pathname of the download file.
            /// </value>
            [Description("This is the Path that is prepended to the Patterns.")]
            [Category("Paths")]
            public String DownloadPath { get; set; }

            /// <summary>
            /// This is the Filename Pattern that is appended to the DownloadPath + DirPattern.
            /// </summary>
            ///
            /// <value>
            /// The file pattern.
            /// </value>
            [Description("This is the Filename Pattern that is appended to the DownloadPath + DirPattern.\r\n\r\nPress F1 for available Patterns.")]
            [Category("Patterns")]
            public String FilePattern { get; set; }

            /// <summary>
            /// This is the Modifier that is appended to the Url to retrieve Original Sized Images.
            /// </summary>
            ///
            /// <value>
            /// The full size download modifier.
            /// </value>
            [Description("This is the Modifier that is appended to the Url to retrieve Original Sized Images.")]
            [Category("Tweaks")]
            public String FullSizeDownloadModifier { get; private set; }

            /// <summary>
            /// This is the HighLight Color of Preview.
            /// </summary>
            ///
            /// <value>
            /// The color of the high light.
            /// </value>
            [Description("This is the HighLight Color of Preview.")]
            [Category("Tweaks")]
            [DefaultValue(typeof(Color), "Yellow")]
            public Color HighLightColor { get; set; }

            /// <summary>
            /// This is the path to the Ini File.
            /// </summary>
            ///
            /// <value>
            /// The full pathname of the initialise file.
            /// </value>
            [Description("This is the path to the Ini File.")]
            [Category("Paths")]
            public String IniPath
            {
                get
                {
                    return Path.Combine(IniFile.AppData, IniFile.AppIni);
                }
            }

            /// <summary>
            /// This is the Path to the My Pictures Directory.
            /// </summary>
            ///
            /// <value>
            /// The pictures.
            /// </value>
            [Description("This is the Path to the My Pictures Directory.")]
            [Category("Paths")]
            public String Pictures
            {
                get
                {
                    return Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
                }
            }

            /// <summary>
            /// This is the Text Color of Preview.
            /// </summary>
            ///
            /// <value>
            /// The color of the text.
            /// </value>
            [Description("This is the Text Color of Preview.")]
            [Category("Tweaks")]
            [DefaultValue(typeof(Color), "LightGray")]
            public Color TextColor { get; set; }

            #endregion Properties

            #region Methods

            /// <summary>
            /// Converts a Color to Xml Format.
            /// </summary>
            ///
            /// <param name="color">     The Color to convert. </param>
            /// <param name="forceHtml"> if true html format is always used. </param>
            ///
            /// <returns>
            /// The converted Color.
            /// </returns>
            public static String ColorToXmlAttribute(Color color, Boolean forceHtml = false)
            {
                if (color.IsNamedColor && !forceHtml)
                {
                    return color.Name;
                }
                else
                {
                    return String.Format("#{0:x2}{1:x2}{2:x2}", color.R, color.G, color.B);
                }
            }

            /// <summary>
            /// Converts a html color bacl to a .NET Color.
            /// </summary>
            ///
            /// <param name="color"> The color. </param>
            ///
            /// <returns>
            /// A Color.
            /// </returns>
            public static Color XmlAttributeToColor(String color)
            {
                if (color.StartsWith("#"))
                {
                    return Color.FromArgb(Int32.Parse(color.Substring(1), NumberStyles.HexNumber));
                }
                else
                {
                    return Color.FromName(color);
                }
            }

            /// <summary>
            /// Load Options from Application IniFile.
            /// </summary>
            public void LoadOptions()
            {
                using (IniFile ini = new IniFile(null))
                {
                    DownloadPath = ini.ReadString("Setup", "DownloadPath", DownloadPath);
                    DirPattern = ini.ReadString("Setup", "DirPattern", DirPattern);
                    FilePattern = ini.ReadString("Setup", "FilePattern", FilePattern);
                    FullSizeDownloadModifier = ini.ReadString("Setup", "FullSizeDownloadModifier", FullSizeDownloadModifier);
                    BackGroundColor = XmlAttributeToColor(ini.ReadString("Setup", "BackGroundColor", ColorToXmlAttribute(BackGroundColor)));
                    TextColor = XmlAttributeToColor(ini.ReadString("Setup", "TextColor", ColorToXmlAttribute(TextColor)));
                }
            }

            /// <summary>
            /// Save Options to Application IniFile.
            /// </summary>
            public void SaveOptions()
            {
                using (IniFile ini = new IniFile(null))
                {
                    ini.WriteString("Setup", "DownloadPath", DownloadPath);
                    ini.WriteString("Setup", "DirPattern", DirPattern);
                    ini.WriteString("Setup", "FilePattern", FilePattern);
                    ini.WriteString("Setup", "FullSizeDownloadModifier", FullSizeDownloadModifier);
                    ini.WriteString("Setup", "BackGroundColor", ColorToXmlAttribute(BackGroundColor));
                    ini.WriteString("Setup", "TextColor", ColorToXmlAttribute(TextColor));

                    ini.UpdateFile();
                }
            }

            #endregion Methods
        }

        #endregion Nested Types
    }
}